package com.iwhalecloud.citybrain.flow.platform.manager.application.event;

import com.alibaba.fastjson.JSON;
import com.iwhalecloud.citybrain.flow.platform.manager.application.utils.SpringUtils;
import com.iwhalecloud.citybrain.flow.platform.manager.application.utils.StringUtils;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.exception.EventCallException;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.Future;

/**
 * @author: liyang
 * @date: 2021/11/14 22:29
 */
@Component
@Slf4j
public class NodeEventExecuter<T> {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private EventExceptionProcessor eventExceptionProcessor;

    public EventResponse<T> syncCall(String url,String requestBody) throws EventCallException {
        log.info("start syncCall");
        return execute(url,requestBody);
    }

    @Async("asyncEventThreadPoolTaskExecutor")
    public Future<EventResponse> asyncCall(String url, String requestBody) throws EventCallException {
        log.info("start asyncCall");
        return new AsyncResult<>(execute(url,requestBody));
    }

    private EventResponse execute(String url,String requestBody) throws EventCallException {
        log.info(String.format("EventCall req: url = %s, requestBody = %s",url,requestBody));
        ResponseEntity<EventResponse> eventResponse = null;
        try {
            eventResponse = restTemplate.exchange(url, HttpMethod.POST,buildHttpEntiry(requestBody), EventResponse.class);
        } catch (RestClientException e) {
            log.error("execute event call error:",e);
            // 只有当重试还是失败时,则需要记录执行的异常日志
            // 为空时,对象实例化
            if (Objects.isNull(eventExceptionProcessor)){
                eventExceptionProcessor = SpringUtils.getBean(EventExceptionProcessor.class);
            }
            // 如果异常信息不为空,则取出异常信息进行记录
            eventExceptionProcessor.insertEventExceptionInfo(getStackTrace(e), url, requestBody);
            throw new EventCallException("事件接口调用异常："+ StringUtils.changeStringEncodingFromGBKToUTF8(e.getMessage()));
        }
        if (Objects.nonNull(eventResponse)){
            if (!Objects.equals(HttpStatus.SC_OK,eventResponse.getStatusCodeValue())){
                // 只有当重试还是失败时,则需要记录执行的异常日志
                // 为空时,对象实例化
                if (Objects.isNull(eventExceptionProcessor)){
                    eventExceptionProcessor = SpringUtils.getBean(EventExceptionProcessor.class);
                }
                // 如果异常信息不为空,则取出异常信息进行记录
                eventExceptionProcessor.insertEventExceptionInfo("事件接口调用失败", url, requestBody);
                throw new EventCallException("事件接口调用失败");
            }else{
                log.info("call Event success!");
            }
            log.info("eventResponse:"+ JSON.toJSONString(eventResponse));
            return eventResponse.getBody();
        }
        return null;
    }

    public EventResponse<T> syncCall(String url,String requestBody,String processInstanceId, String nodeCode,String modelId,String modelKey,String modelName,String name,String deploymentId,String callbackType,String eventType) throws EventCallException {
        log.info("start syncCall");
        return execute(url,requestBody,processInstanceId,nodeCode,"同步",modelId,modelKey,modelName,name,deploymentId,callbackType,eventType);
    }

    @Async("asyncEventThreadPoolTaskExecutor")
    public Future<EventResponse> asyncCall(String url, String requestBody,String processInstanceId, String nodeCode,String modelId,String modelKey,String modelName,String name,String deploymentId,String callbackType,String eventType) throws EventCallException {
        log.info("start asyncCall");
        return new AsyncResult<>(execute(url,requestBody,processInstanceId,nodeCode,"异步",modelId,modelKey,modelName,name,deploymentId,callbackType,eventType));
    }
    private EventResponse execute(String url,String requestBody,String processInstanceId, String nodeCode,String callMethod,String modelId,String modelKey,String modelName,String name,String deploymentId,String callbackType,String eventType) throws EventCallException {
        log.info(String.format("EventCall req: url = %s, requestBody = %s",url,requestBody));
        ResponseEntity<EventResponse> eventResponse = null;
        try {
            eventResponse = restTemplate.exchange(url, HttpMethod.POST,buildHttpEntiry(requestBody), EventResponse.class);
        } catch (RestClientException e) {
            log.error("execute event call error:",e);
            // 只有当重试还是失败时,则需要记录执行的异常日志
            // 为空时,对象实例化
            if (Objects.isNull(eventExceptionProcessor)){
                eventExceptionProcessor = SpringUtils.getBean(EventExceptionProcessor.class);
            }
            // 如果异常信息不为空,则取出异常信息进行记录
            eventExceptionProcessor.insertEventExceptionInfo(getStackTrace(e), url, requestBody,processInstanceId,nodeCode,callMethod,modelId,modelKey,modelName,name,deploymentId,callbackType,eventType);
            throw new EventCallException("事件接口调用异常："+ StringUtils.changeStringEncodingFromGBKToUTF8(e.getMessage()));
        }
        if (Objects.nonNull(eventResponse)){
            if (!Objects.equals(HttpStatus.SC_OK,eventResponse.getStatusCodeValue())){
                // 只有当重试还是失败时,则需要记录执行的异常日志
                // 为空时,对象实例化
                if (Objects.isNull(eventExceptionProcessor)){
                    eventExceptionProcessor = SpringUtils.getBean(EventExceptionProcessor.class);
                }
                // 如果异常信息不为空,则取出异常信息进行记录
                eventExceptionProcessor.insertEventExceptionInfo("事件接口调用失败", url, requestBody,processInstanceId,nodeCode,callMethod,modelId,modelKey,modelName,name,deploymentId,callbackType,eventType);
                throw new EventCallException("事件接口调用失败");
            }else{
                log.info("call Event success!");
            }
            log.info("eventResponse:"+ JSON.toJSONString(eventResponse));
            return eventResponse.getBody();
        }
        return null;
    }

    private HttpEntity<String> buildHttpEntiry(String requestBody){
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setAcceptCharset(Collections.singletonList(Charset.forName("utf-8")));
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        return new HttpEntity(requestBody,headers);
    }

    /**
     * 获取错误的堆栈信息
     * @param throwable
     * @return
     */
    public String getStackTrace(Throwable throwable){
        StringWriter stringWriter=new StringWriter();
        PrintWriter printWriter=new PrintWriter(stringWriter);
        try{
            throwable.printStackTrace(printWriter);
            String str = stringWriter.toString();
            return StringUtils.changeStringEncodingFromGBKToUTF8(str);
        } finally {
            printWriter.close();
        }
    }
}
